package org.opens.stock.controller;

import lombok.extern.slf4j.Slf4j;
import org.opens.stock.constant.LockConstant;
import org.opens.util.RedisUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RequestMapping(value = "/stock")
@RestController
@Slf4j
public class StockController {

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private Redisson redisson;

    /**
     * 基础无锁版本的扣减库存接口
     */
    @RequestMapping(value = "/deductStock")
    public String deductStock() {
        int stock = 0;
        String stockRedisValue = redisUtils.getString("stock");
        if (stockRedisValue == null) {
            int defaultStock = 100;
            // 设置默认值
            redisUtils.set("stock", defaultStock);
            stock = defaultStock;
        } else {
            stock = Integer.parseInt(stockRedisValue);
        }
        log.info("从库中读取到的库存: {}", stock);

        if (stock > 0) {
            int realCount = stock - 1;
            redisUtils.set("stock", realCount);
            return "扣减成功, 剩余库存: " + realCount;
        }

        return "扣减失败, 库存不足";
    }

    /**
     * 加上锁控制的扣减库存操作
     */
    @RequestMapping(value = "/deductStockUseLocalLock")
    public String deductStockUseLocalLock() {
        synchronized (LockConstant.STOCK_LOCK) {
            int stock = 0;
            String stockRedisValue = redisUtils.getString("stock");
            if (stockRedisValue == null) {
                int defaultStock = 100;
                // 设置默认值
                redisUtils.set("stock", defaultStock);
                stock = defaultStock;
            } else {
                stock = Integer.parseInt(stockRedisValue);
            }
            log.info("从库中读取到的库存: {}", stock);

            if (stock > 0) {
                int realCount = stock - 1;
                redisUtils.set("stock", realCount);
                return "扣减成功, 剩余库存: " + realCount;
            }

            return "扣减失败, 库存不足";
        }
    }

    /**
     * 加上基于redis分布式锁的扣减库存操作
     */
    @RequestMapping(value = "/deductStockUseRedisLock")
    public String deductStockUseRedisLock() {
        // 相当于redis的setnx key value命令
        String lockKey = "lockKey";
        // 尝试获取锁
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhangsan");
        if (!flag) {
            return "获取锁失败!";
        }

        // 下面的代码就不需要加synchronized了
        int stock = 0;
        String stockRedisValue = redisUtils.getString("stock");
        if (stockRedisValue == null) {
            int defaultStock = 100;
            // 设置默认值
            redisUtils.set("stock", defaultStock);
            stock = defaultStock;
        } else {
            stock = Integer.parseInt(stockRedisValue);
        }
        log.info("从库中读取到的库存: {}", stock);

        if (stock > 0) {
            int realCount = stock - 1;
            redisUtils.set("stock", realCount);
            return "扣减成功, 剩余库存: " + realCount;
        }

        // 锁使用完毕后进行删除
        stringRedisTemplate.delete(lockKey);

        return "扣减失败, 库存不足";
    }

    /**
     * 加上基于redis分布式锁的扣减库存操作2
     */
    @RequestMapping(value = "/deductStockUseRedisLock2")
    public String deductStockUseRedisLock2() {
        // 相当于redis的setnx key value命令
        String lockKey = "lockKey";

        try {
            // 分开写还是会有宕机问题
            /*// 尝试获取锁
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhangsan");
            stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);*/

            // 给锁加上过期时间, 兼容程序当即后锁无法被释放问题
            // 要保证设置锁key和设置超时时间这两个操作的原子性
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhangsan", 10, TimeUnit.SECONDS);
            if (!flag) {
                return "获取锁失败!";
            }

            // 下面的代码就不需要加synchronized了
            int stock = 0;
            String stockRedisValue = redisUtils.getString("stock");
            if (stockRedisValue == null) {
                int defaultStock = 100;
                // 设置默认值
                redisUtils.set("stock", defaultStock);
                stock = defaultStock;
            } else {
                stock = Integer.parseInt(stockRedisValue);
            }
            log.info("从库中读取到的库存: {}", stock);

            if (stock > 0) {
                int realCount = stock - 1;
                redisUtils.set("stock", realCount);
                return "扣减成功, 剩余库存: " + realCount;
            }
        } catch (Exception e) {
            log.error("扣减库存期间发生异常!", e);
        } finally {
            // 锁使用完毕后进行删除
            stringRedisTemplate.delete(lockKey);
        }

        return "扣减失败, 库存不足";
    }

    /**
     * 加上基于redis分布式锁的扣减库存操作2
     */
    @RequestMapping(value = "/deductStockUseRedisLock3")
    public String deductStockUseRedisLock3() {
        // 相当于redis的setnx key value命令
        String lockKey = "lockKey";
        String clientId = UUID.randomUUID().toString();

        try {
            // 分开写还是会有宕机问题
            /*// 尝试获取锁
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "zhangsan");
            stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);*/

            // 给锁加上过期时间, 兼容程序当即后锁无法被释放问题
            // 要保证设置锁key和设置超时时间这两个操作的原子性
            // 将锁的值和当前线程关联起来
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
            if (!flag) {
                return "获取锁失败!";
            }

            // 下面的代码就不需要加synchronized了
            int stock = 0;
            String stockRedisValue = redisUtils.getString("stock");
            if (stockRedisValue == null) {
                int defaultStock = 100;
                // 设置默认值
                redisUtils.set("stock", defaultStock);
                stock = defaultStock;
            } else {
                stock = Integer.parseInt(stockRedisValue);
            }
            log.info("从库中读取到的库存: {}", stock);

            if (stock > 0) {
                int realCount = stock - 1;
                redisUtils.set("stock", realCount);
                return "扣减成功, 剩余库存: " + realCount;
            }
        } catch (Exception e) {
            log.error("扣减库存期间发生异常!", e);
        } finally {
            // 将锁的值和当前线程关联起来后, 判断锁的值是自己的锁时才删除, 这样就避免掉了业务代码超过锁超时时间导致的当前线程删除其他线程锁的问题
            if (Objects.equals(clientId, stringRedisTemplate.opsForValue().get(lockKey))) {
                // 锁使用完毕后进行删除
                stringRedisTemplate.delete(lockKey);
            }
        }

        return "扣减失败, 库存不足";
    }

    /**
     * 加上基于redisson分布式锁的扣减库存操作
     */
    @RequestMapping(value = "/deductStockUseRedissonLock")
    public String deductStockUseRedissonLock() {
        // 相当于redis的setnx key value命令
        String lockKey = "lockKey";
        // 获取锁对象
        RLock lock = redisson.getLock(lockKey);

        try {
            // 加锁
            lock.lock();

            // 下面的代码就不需要加synchronized了
            int stock = 0;
            String stockRedisValue = redisUtils.getString("stock");
            if (stockRedisValue == null) {
                int defaultStock = 100;
                // 设置默认值
                redisUtils.set("stock", defaultStock);
                stock = defaultStock;
            } else {
                stock = Integer.parseInt(stockRedisValue);
            }
            log.info("从库中读取到的库存: {}", stock);

            if (stock > 0) {
                int realCount = stock - 1;
                redisUtils.set("stock", realCount);
                return "扣减成功, 剩余库存: " + realCount;
            }
        } catch (Exception e) {
            log.error("扣减库存期间发生异常!", e);
        } finally {
            // 释放锁
            lock.unlock();
        }

        return "扣减失败, 库存不足";
    }


}
